package com.loser.gateway.biz;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.loser.gateway.utils.ChannelUtils;
import com.loser.module.base.proto.broadcast.BroadCastMsg;
import com.loser.module.base.proto.sdk.gateway.request.HeartbeatReq;
import com.loser.redis.core.RedisHandler;
import io.netty.channel.Channel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

@Component
public class OnlineHandler {

    private final String PRE = "GATEWAY:ONLINE:";
    public final String ALL = PRE + "AREA:ALL";
    public final String AREA = PRE + "AREA:%s";
    public final String ROOM = PRE + "ROOM:%s:%s";
    private final Map<Integer, Function<BroadCastMsg, Map<String, Channel>>> functionMap = build();

    @Autowired
    private RedisHandler redisHandler;

    private Map<Integer, Function<BroadCastMsg, Map<String, Channel>>> build() {

        // 1=全服 2=区域 3=房间 4=用户
        Map<Integer, Function<BroadCastMsg, Map<String, Channel>>> map = new ConcurrentHashMap<>();
        map.put(1, this::handleAll);
        map.put(2, this::handleArea);
        map.put(3, this::handleRoom);
        map.put(4, this::handleUser);
        return map;

    }

    private Map<String, Channel> handleAll(BroadCastMsg req) {

        Set<String> uIds = redisHandler.zrevrange(ALL, 0, -1);
        return ChannelUtils.getChangesByUserIds(uIds);

    }

    private Map<String, Channel> handleArea(BroadCastMsg req) {

        List<String> targetIds = req.getTargetIds();
        if (CollectionUtil.isEmpty(targetIds)) {
            return Maps.newHashMap();
        }
        Set<String> uIds = Sets.newHashSet();
        for (String targetId : targetIds) {
            String areaKey = buildKey(AREA, targetId);
            Set<String> ids = redisHandler.zrevrange(areaKey, 0, -1);
            if (CollectionUtil.isNotEmpty(ids)) {
                uIds.addAll(ids);
            }
        }
        return ChannelUtils.getChangesByUserIds(uIds);

    }

    private Map<String, Channel> handleRoom(BroadCastMsg req) {

        List<String> targetIds = req.getTargetIds();
        if (CollectionUtil.isEmpty(targetIds)) {
            return Maps.newHashMap();
        }
        Set<String> uIds = Sets.newHashSet();
        for (String targetId : targetIds) {
            String roomKey = buildKey(ROOM, req.getRoomType(), targetId);
            Set<String> ids = redisHandler.zrevrange(roomKey, 0, -1);
            if (CollectionUtil.isNotEmpty(ids)) {
                uIds.addAll(ids);
            }
        }
        return ChannelUtils.getChangesByUserIds(uIds);

    }

    private Map<String, Channel> handleUser(BroadCastMsg req) {
        return ChannelUtils.getChangesByUserIds(req.getTargetIds());
    }

    private String buildKey(String key, Object... args) {
        return String.format(key, args);
    }

    public void handleHeartbeat(HeartbeatReq heartbeatReq) {

        long now = System.currentTimeMillis();
        String userId = Convert.toStr(heartbeatReq.getUserId());
        String roomType = heartbeatReq.getRoomType();
        Long roomId = heartbeatReq.getRoomId();

        redisHandler.zadd(ALL, now, userId);
        if (StringUtils.isNotEmpty(roomType)) {
            String areaKey = buildKey(AREA, roomType);
            redisHandler.zadd(areaKey, now, userId);
            if (Objects.nonNull(roomId)) {
                String roomKey = buildKey(ROOM, roomType, roomId);
                redisHandler.zadd(roomKey, now, userId);
            }
        }

    }

    public Map<String, Channel> getBroadcastChannels(BroadCastMsg msg) {

        Function<BroadCastMsg, Map<String, Channel>> handler = functionMap.get(msg.getType());
        if (Objects.isNull(handler)) {
            return Maps.newHashMap();
        }
        return handler.apply(msg);

    }
}
